package form

import (
	"fmt"
	"gitee.com/gcom/gbox/tag"
	"log"
	"reflect"
)

type converterCache struct {
	// map for converter of type
	tc map[reflect.Type]ConvertHandler
	// map for named converter
	nc map[string]ConvertHandler
}

func newConverterCache() *converterCache {
	cc := &converterCache{
		tc: make(map[reflect.Type]ConvertHandler),
		nc: make(map[string]ConvertHandler),
	}
	for k, v := range builtinTypeConverters {
		cc.tc[k] = v
	}
	return cc
}

type converterInfo struct {
	// type for convert handler to process
	ty reflect.Type
	// origin type before unwrap slice or ptr for convert handler to process
	oty reflect.Type
	// handler for the type
	h ConvertHandler
	// dimension to unwrap for slice or array field
	dim int
	// pointer level to wrap, can be -1, 0, 1.
	ptrWrap int
	// whether convert is builtin
	builtin bool
	// convert multiple value for array or slice
	convMultiple bool
}

func (c *converterInfo) convert(v reflect.Value, value []string, opt tag.TagOptions) error {
	if v.Kind() == reflect.Ptr {
		if v.IsNil() {
			v.Set(reflect.New(v.Type().Elem()))
		}
		v = v.Elem()
	}
	if !c.convMultiple {
		return c.h(v, value[len(value)-1], opt)
	}
	if v.Len() < len(value) {
		if v.Kind() == reflect.Array {
			return fmt.Errorf("index out of range %d, %v", v.Len(), value)
		}
		s := reflect.MakeSlice(v.Type(), len(value), len(value))
		v.Set(s)
	}
	for i, val := range value {
		p := reflect.New(v.Type().Elem())
		vi := p.Elem()
		err := error(nil)
		if c.ptrWrap <= 0 {
			n := -c.ptrWrap
			vin := vi
			for n > 0 {
				n -= 1
				if vin.IsNil() {
					_v := reflect.New(vin.Type().Elem())
					vin.Set(_v)
				}
				vin = vin.Elem()
			}
			err = c.h(vin, val, opt)
		} else if c.ptrWrap == 1 {
			err = c.h(p, val, opt)
		}
		if err != nil {
			return err
		}
		v.Index(i).Set(vi)
	}
	return nil
}

const namedConverterKey = "converter"

func (c *converterCache) registerTypeConverter(t reflect.Type, h ConvertHandler) {
	c.tc[t] = h
}

func (c *converterCache) registerNamedConverter(name string, h ConvertHandler) {
	c.nc[name] = h
}

func (c *converterCache) getConvertHandler(f *FieldMeta, indices []int) *converterInfo {
	ty, err := unwrapDim(f.Typ, len(indices))
	if err != nil {
		return nil
	}
	ci := &converterInfo{
		ty:      ty,
		oty:     f.Typ,
		dim:     len(indices),
		ptrWrap: 0,
	}
	tv := f.Tag[namedConverterKey]
	if s, ok := tv.(string); ok {
		ci.h = c.nc[s]
	} else if args, ok := tv.(map[string]interface{}); ok {
		if n, ok := args["name"]; ok {
			if name, ok := n.(string); ok {
				ci.h = c.nc[name]
			} else {
				log.Printf("wrong argument value of converter.name for form tag of %s: %v\n", f.Name, n)
				return nil
			}
			if ci.h != nil {
				if p, ok := args["ptrWrap"]; ok {
					if _d, ok := p.(int64); ok && _d <= 1 && _d >= -1 {
						ci.ptrWrap = int(_d)
					} else {
						log.Printf("wrong argument value of converter.ptrWrap for form tag of %s: %v\n", f.Name, p)
						return nil
					}
				}
			}
		}
	}

	if ci.h != nil {
		return ci
	}
	return c.getTypeHandler(ty, []int{})
}

func (c *converterCache) getTypeHandler(ty reflect.Type, indices []int) *converterInfo {
	ty, err := unwrapDim(ty, len(indices))
	if err != nil {
		return nil
	}
	ci := &converterInfo{
		ty:      ty,
		oty:     ty,
		dim:     len(indices),
		ptrWrap: 0,
	}
	ci.h = c.tc[ty]
	if ci.h == nil {
		// check ptr element
		if ty.Kind() == reflect.Ptr {
			t0 := ty
			for t0.Kind() == reflect.Ptr && ci.h == nil {
				t0 = t0.Elem()
				ci.h = c.tc[t0]
				ci.ptrWrap -= 1
				ci.ty = t0
			}
			if ci.h == nil {
				ci.h = builtinConverters[t0.Kind()]
				if ci.h != nil {
					ci.builtin = true
				}
			}
		} else {
			ci.h = builtinConverters[ty.Kind()]
			if ci.h != nil {
				ci.builtin = true
			}
		}
		// check type ptr
		if ci.h == nil {
			t0 := reflect.PtrTo(ty)
			ci.h = c.tc[t0]
			ci.ptrWrap = 1
			ci.ty = t0
		}
	}
	if ci.h != nil {
		return ci
	}
	dim, _ := getDimAndType(ty)
	if dim == 1 {
		indices = append(indices, 0)
		ci = c.getTypeHandler(ty, indices)
		if ci != nil {
			ci.convMultiple = true
		}
		return ci
	}
	return nil
}

func unwrapDim(t reflect.Type, dim int) (reflect.Type, error) {
	for dim > 0 {
		t = indirectPtr(t)
		if t.Kind() == reflect.Slice || t.Kind() == reflect.Array {
			t = t.Elem()
			dim--
		} else {
			return nil, fmt.Errorf("invalid index dim of type %v", t)
		}
	}
	return t, nil
}

func indirectPtr(typ reflect.Type) reflect.Type {
	for typ.Kind() == reflect.Ptr {
		typ = typ.Elem()
	}
	return typ
}

func getDimAndType(ty reflect.Type) (int, reflect.Type) {
	var dim int
	var unwrapPtr = func(t reflect.Type) reflect.Type {
		pl := 0
		for ty.Kind() == reflect.Ptr {
			pl += 1
			ty = ty.Elem()
		}
		return ty
	}
	ty = unwrapPtr(ty)
	for ty.Kind() == reflect.Slice || ty.Kind() == reflect.Array {
		dim += 1
		ty = ty.Elem()
		ty = unwrapPtr(ty)
	}
	return dim, ty
}
